﻿-------------------------------------------------------------------------------
-- Addon - Runes - Init
-------------------------------------------------------------------------------
-- Version
local MAJOR = "Avion2_Runes"
local MINOR = tonumber(("@project-revision@"):match("%d+")) or 0

-- Dependencies
if not Avion2Core 				then error(MAJOR .. " requires Avion2.") end
if not LibStub("AceEvent-3.0")	then error(MAJOR .. " requires AceEvent-3.0.") end

-- Create
Avion2_Runes 		= Avion2Core:NewModule(MAJOR, "AceEvent-3.0")
Avion2_Runes.major 	= MAJOR
Avion2_Runes.minor 	= MINOR


-------------------------------------------------------------------------------
-- Addon - Runes - Variables
-------------------------------------------------------------------------------
-- Translation
local L 	= LibStub("AceLocale-3.0"):GetLocale(MAJOR)
local coreL	= LibStub("AceLocale-3.0"):GetLocale(Avion2Core.major)

-- Local functions
local UpdatePower, UpdateRunes, GetRuneCount


-------------------------------------------------------------------------------
-- Addon - Runes - Register with core
-------------------------------------------------------------------------------
-- Register SavedVariables
function Avion2_Runes:RegisterModuleDB()
	-- Register own saved variables DB
	self.db = Avion2Core.db:RegisterNamespace(Avion2_Runes.major, {
		profile = {
			power	= {
				data 			= {},
				onlyOneActive 	= false,
			},
			runes	= {
				[1] 		= {					-- Blood
					data 			= {},
					onlyOneActive 	= false,
				},
				[2]	= {							-- Unholy/Chromatic
					data 			= {},
					onlyOneActive 	= false,
				},
				[3] 		= {					-- Frost
					data 			= {},
					onlyOneActive 	= false,
				},
			},
			lastMinor	= MINOR,
		},
	})
	
	-- Last Runes
	self.lastRunes = {
		[1] 		= 0,
		[2] 	= 0,
		[3] 		= 0,
	}
	
	-- Track last energy value
	self.lastPower = 0
	
	-- Updateto to new rev, data converters should go here
	self.db.profile.lastMinor = Avion2_Runes.minor
end


-- (Un)register (un)needed events
function Avion2_Runes:UpdateEventRegistration()
	local enabled = false
	for key, value in pairs(self.db.profile.power.data) do
		if (#value.animationList > 0) then
			enabled = true
		end
	end
	
	-- Enable each event if there is something to watch
	if (self:IsEnabled() and enabled) then
		self:RegisterEvent("UNIT_RUNIC_POWER", UpdatePower)
	else
		self:UnregisterEvent("UNIT_RUNIC_POWER")
	end
	
	enabled = false
	for runeType, runeData in pairs(self.db.profile.runes) do
		for key, value in pairs(runeData.data) do
			if (#value.animationList > 0) then
				enabled = true
			end
		end
	end
	
	-- Enable each event if there is something to watch
	--if (self:IsEnabled() and enabled) then
	if (true) then
		self:RegisterEvent("RUNE_POWER_UPDATE", UpdateRunes)
		self:RegisterEvent("RUNE_TYPE_UPDATE", UpdateRunes)
	else
		self:UnregisterEvent("RUNE_POWER_UPDATE")
		self:UnregisterEvent("RUNE_TYPE_UPDATE")
	end
end


-- Update animation (convert animationFunc: string name to function pointer)
function Avion2_Runes:LoadAnimationTables()
	for key, value in pairs(self.db.profile.power.data) do
		Avion2Core:LoadAnimationTables(value.animationList)
	end
	
	for runeType, runeData in pairs(self.db.profile.runes) do
		for key, value in pairs(runeData.data) do
			Avion2Core:LoadAnimationTables(value.animationList)
		end
	end
end


-------------------------------------------------------------------------------
-- Addon - Runes - Options
-------------------------------------------------------------------------------
-- Fetch animationTable from animationID (as suplied by eg. Menu_Reuqest)
function Avion2_Runes:GetAnimationTable(animationID)
	success, _, arg1, arg2, arg3, arg4 = self:Deserialize(animationID)
	
	if (success) then
		if (arg1 == "power") then
			return self.db.profile.power.data[arg2].animationList[arg3]
		elseif (arg1 == "runes") then
			return self.db.profile.runes[arg2].data[arg3].animationList[arg4]
		else
			return false
		end
	else
		return false
	end
end


-- Build Optionstable
function Avion2_Runes:UpdateOptionTable(args)
	-- Update options when enabled and initialized
	if (self.db and self:IsEnabled()) then
		-- Options for power
		args[self.major.."_Power"] = {
			type = 'group',
			name = L["PowerThresholds"],
			desc = L["PowerThresholds_Desc"],
			args = {
				new = {
					order = 1,
					type = 'input',
					name = L["NewThreshold"],
					desc = L["NewPowerThreshold_Desc"],
					usage = L["NewPowerThreshold_Usage"],
					pattern = "(%d)",
					get = function()
						return ""
					end,
					set = function(info, val)
						if (not self.db.profile.power.data[tonumber(val)]) then
							self.db.profile.power.data[tonumber(val)] = {
								animationList 	= {},
								cancelAnimation	= 0,
							}
							
							self:UpdateEventRegistration()
							Avion2Core:UpdateModuleOptionTable(self)
						end
					end,
				},
				onlyOneActive = {
					order = 2,
					type = 'toggle',
					name = L["OnlyOneActive"],
					desc = L["OnlyOneActive_Desc"],
					get = function()
						return self.db.profile.power.onlyOneActive
					end,
					set = function()
						self.db.profile.power.onlyOneActive = not self.db.profile.power.onlyOneActive
					end,
				},
			},
			icon = self:GetIconPath() .. "Power"
		}		
		-- Options for each power threshold
		for threshold, data in pairs(self.db.profile.power.data) do
			args[self.major.."_Power"].args[string.format("Threshold_%d", threshold)] = {
				type = 'group',
				name = string.format(L["PowerThresholdDetails"], threshold),
				desc = string.format(L["PowerThresholdDetails_Desc"], threshold),
				args = { 
					new = Avion2Core:GetDefaultNewAnimationOption(data.animationList, self),
					delete = {
						order = 2,
						type = 'execute',
						name = L["DeleteThreshold"],
						desc = L["DeleteThreshold_Desc"],
						func = function()
							Avion2Core:StopAnimationList(string.format("Avion2_PowerThreshold_%d", threshold), data.animationList, false)
							self.db.profile.power.data[threshold] = nil
							
							self:UpdateEventRegistration()
							Avion2Core:UpdateModuleOptionTable(self)
						end,
					},
					cancelAnimation = {
						order = 3,
						type = 'select',
						name = coreL["CancelAnimation"],
						desc = coreL["CancelAnimation_Desc"],
						values = {
							[0] = coreL["DontCancel"],
							[1] = coreL["FadeOut"],
							[2] = coreL["Cancel"],
						},
						get = function()
							return data.cancelAnimation
						end,
						set = function(info, val)
							data.cancelAnimation = val
						end,
					},
				},
			}
			-- Add options for each animation
			for animationListID, _ in pairs(data.animationList) do
				-- Combine all info to find this animation in module db in ONE string
				serializedInfo = self:Serialize(Avion2_Runes.major, "power", threshold, animationListID)
				
				-- Update option table
				args[self.major.."_Power"].args[string.format("Threshold_%d", threshold)].args[string.format("Animation_%d", animationListID)] = Avion2Core:GetDefaultAnimationOption(data.animationList, animationListID, serializedInfo, self)
			end
		end

		-- Options for rune-type
		for runeType, runeInfo in pairs(self.db.profile.runes) do
			args[self.major.."_Runes_" .. runeType] = {
				type = 'group',
				name = L["RunesThresholds"][runeType],
				desc = L["RunesThresholds_Desc"],
				args = {
					new = {
						order = 1,
						type = 'input',
						name = L["NewThreshold"],
						desc = L["NewRunesThreshold_Desc"],
						usage = L["NewRunesThreshold_Usage"],
						pattern = "(%d)",
						get = function()
							return ""
						end,
						set = function(info, val)
							if (not runeInfo.data[tonumber(val)]) then
								runeInfo.data[tonumber(val)] = {
									animationList 	= {},
									cancelAnimation	= 0,
								}
								
								self:UpdateEventRegistration()
								Avion2Core:UpdateModuleOptionTable(self)
							end
						end,
					},
					onlyOneActive = {
						order = 2,
						type = 'toggle',
						name = L["OnlyOneActive"],
						desc = L["OnlyOneActive_Desc"],
						get = function()
							return runeInfo.onlyOneActive
						end,
						set = function()
							runeInfo.onlyOneActive = not runeInfo.onlyOneActive
						end,
					},
				},
				icon = self:GetIconPath() .. "Rune" .. tostring(runeType)
			}		
			-- Options for each runes threshold
			for threshold, data in pairs(runeInfo.data) do
				args[self.major.."_Runes_" .. runeType].args[string.format("Threshold_%d", threshold)] = {
					type = 'group',
					name = string.format(L["RunesThresholdDetails"], threshold),
					desc = string.format(L["RunesThresholdDetails_Desc"], threshold),
					args = { 
						new = Avion2Core:GetDefaultNewAnimationOption(data.animationList, self),
						delete = {
							order = 2,
							type = 'execute',
							name = L["DeleteThreshold"],
							desc = L["DeleteThreshold_Desc"],
							func = function()
								Avion2Core:StopAnimationList(string.format("Avion2_Rune_" .. runeType .. "_Threshold_%d", threshold), data.animationList, false)
								runeInfo.data[threshold] = nil
								
								self:UpdateEventRegistration()
								Avion2Core:UpdateModuleOptionTable(self)
							end,
						},
						cancelAnimation = {
							order = 3,
							type = 'select',
							name = coreL["CancelAnimation"],
							desc = coreL["CancelAnimation_Desc"],
							values = {
								[0] = coreL["DontCancel"],
								[1] = coreL["FadeOut"],
								[2] = coreL["Cancel"],
							},
							get = function()
								return data.cancelAnimation
							end,
							set = function(info, val)
								data.cancelAnimation = val
							end,
						},
					},
				}
				-- Add options for each animation
				for animationListID, _ in pairs(data.animationList) do
					-- Combine all info to find this animation in module db in ONE string
					serializedInfo = self:Serialize(Avion2_Runes.major, "runes", runeType, threshold, animationListID)
					
					-- Update option table
					args[self.major.."_Runes_" .. runeType].args[string.format("Threshold_%d", threshold)].args[string.format("Animation_%d", animationListID)] = Avion2Core:GetDefaultAnimationOption(data.animationList, animationListID, serializedInfo, self)
				end
			end
		end
	-- Disabled, remove options
	else
		args[self.major .. "_Power"] 		= nil
		for runeType, _ in pairs(self.db.profile.runes) do
			args[self.major.."_Runes_" .. runeType] = nil
		end
	end
end


-------------------------------------------------------------------------------
-- Addon - Runes - Events
-------------------------------------------------------------------------------
-- Count number of runes of a certain type
GetRuneCount = function(runeType)
	-- Iterate over all runes (easier than updating/saving count on each Event)
	local runeCount = 0
	for runeID = 1, 6, 1 do
		if (GetRuneType(runeID) == runeType or GetRuneType(runeID) == 4) then
			runeCount = runeCount + 1
		end
	end
	
	return runeCount
end


UpdatePower = function(unidID)
	-- Update only for player events (that have runic power)
	if (unitID == "player" and UnitPowerType("player") == 6) then
		-- Get current amount of runic power
		local unitPower = UnitPower("player")
		
		-- Save fitting triggers
		local cache = {}
		
		-- Test for each db entry if it fits
		for threshold, data in pairs(Avion2_CPE.db.profile.power.data) do
			-- Fitting threshold, add it to list
			if (Avion2_CPE.lastUnitPower < threshold and threshold <= unitPower) then
				table.insert(cache, threshold, true)
			-- Or add to stop-list
			else
				table.insert(cache, threshold, false)
			end
		end
		
		-- Refilter triggers
		if (Avion2_CPE.db.profile.power.onlyOneActive) then
			-- Filter only highest match
			local bestFit = -1
			for threshold, status in pairs(cache) do
				-- Filter only active triggers
				if (status) then
					-- There is allready a better match, "delete"
					if (threshold < bestFit) then
						cache[threshold] 	= false
					-- Found a better match, "delete" best last match
					elseif (threshold > bestFit) then
						cache[bestFit] 		= false
						bestFit 			= threshold
					end
				end
			end
		end
	
		-- Finally start/stop animations
		for threshold, status in pairs(cache) do
			if (status) then
				Avion2Core:TriggerAnimationList(string.format("Avion2_RunicThreshold_%d", threshold), Avion2_CPE.db.profile.power.data[threshold].animationList)
			elseif (Avion2_CPE.db.profile.power.data[threshold].cancelAnimation ~= 0) then
				Avion2Core:StopAnimationList(   string.format("Avion2_RunicThreshold_%d", threshold), Avion2_CPE.db.profile.power.data[threshold].animationList, (Avion2_CPE.db.profile.power.data[threshold].cancelAnimation == 1))
			end
		end
	
		-- Update last unit power so we don't tigger twice
		Avion2_Energy.lastUnitPower = unitPower
	end
end


UpdateRunes = function(_, runeID, useable)
	if (runeID > 0 and runeID < 7) then
		-- Get current amount of runic power
		local runeType	= GetRuneType(runeID)
		local runeCount	= GetRuneCount(runeType)
		
		-- Death rune counts as every runetype
		if (runeType == 4) then
			for runeType_, _ in pairs(Avion2_CPE.db.profile.runes) do
				UpdateRuneType(runeCount, runeType_)
			end
		else
			UpdateRuneType(runeCount, runeType)
		end
	end
end

	
UpdateRuneType = function(runeType, runeCount)
	-- Save fitting triggers
	local cache = {}
	
	-- Test for each db entry if it fits
	for threshold, data in pairs(Avion2_CPE.db.profile.runes[runeType].data) do
		-- Fitting threshold, add it to list
		if (Avion2_CPE.lastRunes[runeType] < threshold and threshold <= runeCount) then
			table.insert(cache, threshold, true)
		-- Or add to stop-list
		else
			table.insert(cache, threshold, false)
		end
	end
	
	-- Refilter triggers
	if (Avion2_CPE.db.profile.runes[runeType].onlyOneActive) then
		-- Filter only highest match
		local bestFit = -1
		for threshold, status in pairs(cache) do
			-- Filter only active triggers
			if (status) then
				-- There is allready a better match, "delete"
				if (threshold < bestFit) then
					cache[threshold] 	= false
				-- Found a better match, "delete" best last match
				elseif (threshold > bestFit) then
					cache[bestFit] 		= false
					bestFit 			= threshold
				end
			end
		end
	end

	-- Finally start/stop animations
	for threshold, status in pairs(cache) do
		if (status) then
			Avion2Core:TriggerAnimationList(string.format("Avion2_Rune_%d_%d", runeType, threshold), Avion2_CPE.db.profile.runes[runeType].data[threshold].animationList)
		elseif (Avion2_CPE.db.profile.runes[runeType].data[threshold].cancelAnimation ~= 0) then
			Avion2Core:StopAnimationList(   string.format("Avion2_Rune_%d_%d", runeType, threshold), Avion2_CPE.db.profile.runes[runeType].data[threshold].animationList, (Avion2_CPE.db.profile.runes[runeType].data[threshold].cancelAnimation == 1))
		end
	end

	-- Update last unit power so we don't tigger twice
	Avion2_Energy.lastRunes[runeType] = runeCount
end
